# !/usr/bin/env python3
# -*- coding: utf-8 -*-

import math
import xml.etree.ElementTree as EleTree
from util.lib import pretty_xml
from util.topo_adapter import get_topo_set, get_time_slot_len, add_src_node_port_to_dst_set
from util.flow_adapter import compute_sched_period, get_route_path
from util.solution_adapter import parse_plan_xml


def _init_inform_set(flow_xml):
    """
    从flow_xml中获取flow_id, delay_para, task_id, cookies和源节点的node_id
    注意，这里的源节点指的是该流量经过的第一跳交换机，而非端系统
    """
    # 首先从flow_xml中提取出所有需要FL api进行映射的流量
    tree = EleTree.parse(flow_xml)
    flow_ele_set = tree.findall('flow')

    inform_set = []

    for flow_ele in flow_ele_set:
        # 首先判断该流量是否需要FL api进行映射
        fl_api_flag = flow_ele.findtext('fl_api_flag')
        # 如果该流量不需要FL api进行映射
        if fl_api_flag == '0':
            continue

        # 延时参数
        delay_para = flow_ele.findtext('delay_para')
        delay_para = int(delay_para)
        # 流id
        flow_id = flow_ele.findtext('stream_id')
        flow_id = int(flow_id)
        task_id = flow_id
        # 链路id
        link_id = list(flow_ele.findall('route'))[1].text
        link_id = int(link_id)

        inform_set.append({
            'flow_id': flow_id,
            'delay_para': delay_para,
            'task_id': task_id,
            'cookies': 10,
            'link_id': link_id,

        })

    return inform_set


def _transform_mac_addr(mac_str):
    mac_set = mac_str.split(":")
    mac_str = ''
    for item in mac_set:
        mac_str += item

    mac_int = int(mac_str, 16)
    return mac_int


def _update_mac_addr(inform_set):
    for inform_ele in inform_set:
        # 将node_id转换成mac地址的中间12位
        node_id = inform_ele['src_node_id']
        first4 = hex(int(node_id) >> 8 & 0xf)[2:]
        mid4 = hex(int(node_id) >> 4 & 0xf)[2:]
        last4 = hex(int(node_id) & 0xf)[2:]
        src_mac = '66:26:62:' + first4 + mid4 + ':' + last4 + '8:00'
        mac_int = _transform_mac_addr(src_mac)
        inform_ele.update({'dmac': mac_int})
    return


def _replace_link_id_with_node_id(plan_set,
                                  topo_set):
    for plan_entry in plan_set:
        link_id = plan_entry['link_id']
        for link in topo_set:
            if link_id == link['link_id']:
                plan_entry.update({
                    'src_node_id': link['src_node_id'],
                    'src_port_id': link['src_port_id']
                })
                del plan_entry['link_id']
    return


def _update_sched_point(plan_set,
                        inform_set):
    # [print(entry) for entry in inform_set]
    for inform_entry in inform_set:
        src_node_id = inform_entry['src_node_id']
        src_port_id = inform_entry['src_port_id']
        flow_id = inform_entry['flow_id']
        tick_set = []
        for plan_entry in plan_set:
            if src_node_id == plan_entry['src_node_id'] and \
                    src_port_id == plan_entry['src_port_id']:
                scheduling_set = plan_entry['scheduling_set']
                for sched in scheduling_set:
                    if sched['flow_id'] == flow_id:
                        tick_set.append(sched['macrotick_id'])
        inform_entry.update(
            {'tick_set': tick_set}
        )
    return


# 根据macrotick长度和发送时间槽，以及delay参数，
# 计算得到每一个time_inform表项的n_pit
def _update_n_pit(inform_set, time_slot_len, hyper_period):
    for inform_entry in inform_set:
        delay_para = inform_entry['delay_para']
        delay_para = int(delay_para)
        n_pit_set = []
        for i in range(len(inform_entry['tick_set'])):
            sched_point = inform_entry['tick_set'][i]
            sched_point = int(sched_point)
            n_pit = sched_point * time_slot_len - delay_para
            if n_pit < 0:
                n_pit = n_pit + hyper_period
            n_pit_set.append(n_pit)
        inform_entry.update(
            {'n_pit_set': n_pit_set}
        )
    return


# 将相同节点上的time_inform表项整合到一起
# 初始化TimeInformTbl类的对象
def _generate_inform_tbl(inform_set):
    for inform_entry in inform_set:
        del inform_entry['flow_id']
        del inform_entry['src_port_id']
        del inform_entry['tick_set']
        del inform_entry['delay_para']

    # 将属于同一节点的inform表项整合到一起
    node_set = []
    for inform_entry in inform_set:
        if inform_entry['src_node_id'] not in node_set:
            node_set.append(inform_entry['src_node_id'])
    # print(node_set)

    inform_set_tmp = inform_set
    inform_set = []
    for node in node_set:
        inform_set.append({
            'node_id': node,
            'inform_entry_set': []
        })

    for entry_tmp in inform_set_tmp:
        node_id = entry_tmp['src_node_id']
        for entry in inform_set:
            if node_id == entry['node_id']:
                del entry_tmp['src_node_id']
                # 目的mac地址的转换
                entry['inform_entry_set'].append(entry_tmp)
                break

    for entry in inform_set:
        entry['inform_entry_set'][0]['n_pit_set'] = sorted(entry['inform_entry_set'][0]['n_pit_set'])

    return inform_set


def _init_inform_tbl_xml(inform_set, inform_tbl_xml):
    root = EleTree.Element('inform_tbl')
    for entry in inform_set:
        node = EleTree.SubElement(root, 'node')
        node_id = EleTree.SubElement(node, 'node_id')
        node_id.text = str(entry['node_id'])
        for item in entry['inform_entry_set']:
            for i in range(len(item['n_pit_set'])):
                en = EleTree.SubElement(node, 'entry')
                valid = EleTree.SubElement(en, 'valid')
                valid.text = '1'
                reserved_0 = EleTree.SubElement(en, 'reserved_0')
                reserved_0.text = '0'
                n_pit = EleTree.SubElement(en, 'n_pit')
                n_pit.text = str(item['n_pit_set'][i])
                reserved_1 = EleTree.SubElement(en, 'reserved_1')
                reserved_1.text = '0'
                dmac = EleTree.SubElement(en, 'dmac')
                text = hex(item['dmac'])[2:]
                text = text.ljust(12, '0')
                text = text[0:2] + ':' + text[2:4] + ':' + text[4:6] + ':' \
                       + text[6:8] + ':' + text[8:10] + ':' + text[10:12]
                dmac.text = text
                task_id = EleTree.SubElement(en, 'task_id')
                task_id.text = str(item['task_id'])
                cookies = EleTree.SubElement(en, 'cookies')
                cookies.text = str(item['cookies'])
    tree = EleTree.ElementTree(root)
    pretty_xml(root, '\t', '\n')
    tree.write(inform_tbl_xml,
               encoding='utf-8',
               xml_declaration=True)
    return


def add_src_node_port_to_inform_set(dst_set, route_set):
    for item in dst_set:
        flow_id = item['flow_id']
        for route in route_set:
            if flow_id == route['flow_id']:
                node_id = route['src_node_id']
                port_id = route['src_port_id']
                item.update({'src_node_id': node_id, 'src_port_id': port_id})
                break
    return


def assemble_inform_tbl(plan_xml,
                        topo_xml,
                        flow_xml,
                        inform_tbl_xml):

    # 1.根据topo_xml，获取topo_set
    topo_set = get_topo_set(topo_xml)
    print("1. topo_set: ")
    [print(entry) for entry in topo_set]
    # 2.获取流的路由路径
    route_set = get_route_path(flow_xml, topo_set)
    print("2. route_set: ")
    [print(entry) for entry in route_set]
    # 3.计算时间槽长度
    time_slot_len = get_time_slot_len(topo_xml)
    print("3. time_slot_len: ", time_slot_len)
    # 4.计算流的调度周期
    sched_period = compute_sched_period(flow_xml)
    print("4. sched_period: ", sched_period)
    # 5.生成plan_set
    plan_set = parse_plan_xml(plan_xml)
    print("5. plan_set: ")
    [print(entry) for entry in plan_set]
    # 6.更新plan_set
    add_src_node_port_to_dst_set(plan_set, topo_set)
    print("6. update plan_set: ")
    [print(entry) for entry in plan_set]

    # 7.从flow_xml中获取flow_id, delay_para, task_id, cookies，生成inform_set
    inform_set = _init_inform_set(flow_xml)
    print("7. inform_set: ")
    [print(entry) for entry in inform_set]

    # 8.更新流的源节点和源端口到inform_set
    add_src_node_port_to_inform_set(inform_set, route_set)
    print("8. update src_node, src_port to inform_set: ")
    [print(entry) for entry in inform_set]
    # 9.更新源节点对应的mac地址到inform_set
    _update_mac_addr(inform_set)
    print("9. update mac_addr to inform_set: ")
    [print(entry) for entry in inform_set]
    # 10.更新sched_point到inform_set
    _update_sched_point(plan_set, inform_set)
    print("10. update sched_point to inform_set: ")
    [print(entry) for entry in inform_set]
    # 11.更新n_pit到inform_set
    _update_n_pit(inform_set, time_slot_len, sched_period)
    print("11. update n_pit to inform_set: ")
    [print(entry) for entry in inform_set]

    # 12.将相同节点上的time_inform表项整合到一起
    inform_set = _generate_inform_tbl(inform_set)

    # 生成xml
    _init_inform_tbl_xml(inform_set, inform_tbl_xml)
    return


def main():
    plan_xml = '../input/solution.xml'
    topo_xml = '../input/topo_feature.xml'
    flow_xml = '../input/flow_feature.xml'
    inform_tbl_xml = '../output/tbl/inform_tbl.xml'

    assemble_inform_tbl(plan_xml,
                        topo_xml,
                        flow_xml,
                        inform_tbl_xml)
    return


if __name__ == '__main__':
    main()
